﻿using System;
using System.Collections.Generic;
using System.IO;
using System.Linq;
using System.Reflection;
using OsamesFrameworkCore.Logging;

namespace OsamesFrameworkCore.Tools
{
    /// <summary>
    /// Utilities using reflection.
    /// </summary>
    public static class ReflectionTools
    {
        /// <summary>
        /// Creates an instance of each class matching "T" parameter as implementing from T (base usage : find all plugins implementing IPlugin),
        /// by looking up into .dll files in parameter directory.
        /// However, class must have a public constructor and not be abstract
        /// </summary>
        /// <typeparam name="T">Most often interface type. Can also be class type.</typeparam>
        /// <param name="strLookupDirectory_">Directory of .dll files</param>
        /// <returns>List of instances which implement T</returns>
        public static List<T> FindPublicConcreteClassImplementing<T>(string strLookupDirectory_) where T : class
        {
            List<T> lstFound = new List<T>();
            string[] strFiles = Directory.GetFiles(strLookupDirectory_);
            int iCount = strFiles.Length;
            for (int i = 0; i < iCount; i++)
            {
                // Preliminary check, must be .dll
                string strFile = strFiles[i];
                if (Path.GetExtension(strFile).ToLowerInvariant() == ".dll")
                {
                    try
                    {
                        Assembly asAssembly = Assembly.LoadFrom(strFiles[i]);
                        lstFound.AddRange(
                            asAssembly.GetTypes().Where(
                                t_ => t_.IsClass && !t_.IsAbstract && t_.IsPublic && t_.GetConstructor(Type.EmptyTypes) != null
                                     && typeof(T).IsAssignableFrom(t_)).Select(
                                         Activator.CreateInstance).OfType<T>().ToList());
                    }
                    catch (ReflectionTypeLoadException ex)
                    {
                        Handle(ex, strFile);
                    }
                }
            }
            return lstFound;
        }

        /// <summary>
        /// Creates an instance of each class matching "T" parameter as implementing from T (base usage : find all plugins implementing IPlugin),
        /// by looking up into .dll files in parameter directory.
        /// However, class must have a public constructor and not be abstract
        /// </summary>
        /// <typeparam name="T">Most often interface type. Can also be class type.</typeparam>
        /// <param name="strLookupDirectory_">Directory of .dll files</param>
        /// <returns>List of instances which implement T</returns>
        public static List<KeyValuePair<string, T>> FindPublicConcreteClassWithPathImplementing<T>(string strLookupDirectory_) where T : class
        {
            List<KeyValuePair<string, T>> lstFound = new List<KeyValuePair<string, T>>();
            string[] strFiles = Directory.GetFiles(strLookupDirectory_);
            int iCount = strFiles.Length;
            for (int i = 0; i < iCount; i++)
            {
                // Preliminary check, must be .dll
                string strFile = strFiles[i];
                if (Path.GetExtension(strFile).ToLowerInvariant() == ".dll")
                {
                    try
                    {
                        Assembly asAssembly = Assembly.LoadFrom(strFile);
                        List<T>.Enumerator eEnumerator =
                            asAssembly.GetTypes().Where(
                                t_ =>
                                t_.IsClass && !t_.IsAbstract && t_.IsPublic &&
                                t_.GetConstructor(Type.EmptyTypes) != null
                                && typeof(T).IsAssignableFrom(t_)).Select(
                                    Activator.CreateInstance).OfType<T>().ToList().GetEnumerator();
                        while (eEnumerator.MoveNext())
                        {
                            lstFound.Add(new KeyValuePair<string, T>(strFile, eEnumerator.Current));
                        }
                    }
                    catch (ReflectionTypeLoadException ex)
                    {
                        Handle(ex, strFile);
                    }
                }
            }
            return lstFound;
        }
        /// <summary>
        /// Logs details of a ReflectionTypeLoadException.
        /// </summary>
        /// <param name="rex_">ReflectionTypeLoadException</param>
        /// <param name="strFileName_">Name of assembly file at the origin of the exception</param>
        private static void Handle(ReflectionTypeLoadException rex_, string strFileName_)
        {
            LoggerService.Log(typeof(ReflectionTools), LogLevel.Error, "Error loading types from assembly (file : {0}), LoaderException : {1}",
                       new object[] { strFileName_, rex_.LoaderExceptions[0].Message });
            LoggerService.LogToOutput(string.Format("Error loading types from assembly (file : {0})", strFileName_));
        }
    }
}
